﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace RaisingStudio.Data.Linq
{
    public class ExpressionCommandBuilder : CommandBuilder
    {
        /// <summary>
        /// Format string of parameter name.
        /// </summary>
        public const string PARAMETERNAMEFORMAT = "@pe{0}";

        #region static
        public static CommonCommand GetCommand(System.Linq.Expressions.Expression expression)
        {
            CommandSegmentType segmentType;
            return (new ExpressionCommandBuilder(expression)).GetCommand(out segmentType);
        }

        public static CommonCommand GetCommand(System.Linq.Expressions.Expression expression, out RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            CommandSegmentType segmentType;
            return (new ExpressionCommandBuilder(expression)).GetCommand(out columns, out segmentType);
        }

        public static CommonCommand GetCommand(System.Linq.Expressions.Expression expression, out string[] columns)
        {
            CommandSegmentType segmentType;
            return (new ExpressionCommandBuilder(expression)).GetCommand(out columns, out segmentType);
        }


        public static CommonCommand GetCommand(RaisingStudio.Data.Common.Builders.CommonCommandBuilder commandBuilder, System.Linq.Expressions.Expression expression)
        {
            CommandSegmentType segmentType;
            return (new ExpressionCommandBuilder(commandBuilder, expression)).GetCommand(out segmentType);
        }

        public static CommonCommand GetCommand(RaisingStudio.Data.Common.Builders.CommonCommandBuilder commandBuilder,  System.Linq.Expressions.Expression expression, out RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            CommandSegmentType segmentType;
            return (new ExpressionCommandBuilder(commandBuilder, expression)).GetCommand(out columns, out segmentType);
        }

        public static CommonCommand GetCommand(RaisingStudio.Data.Common.Builders.CommonCommandBuilder commandBuilder, System.Linq.Expressions.Expression expression, out string[] columns)
        {
            CommandSegmentType segmentType;
            return (new ExpressionCommandBuilder(commandBuilder, expression)).GetCommand(out columns, out segmentType);
        }


        public static RaisingStudio.Data.Expressions.ColumnExpression[] GetColumnExpressions(Expression expression)
        {
            if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Quote))
            {
                expression = (expression as UnaryExpression).Operand;
            }
            if ((expression is LambdaExpression) && (expression.NodeType == ExpressionType.Lambda))
            {
                LambdaExpression l = expression as LambdaExpression;
                Expression e = l.Body;
                if (e.NodeType == ExpressionType.Convert)
                {
                    e = (e as UnaryExpression).Operand;
                }
                if ((e is MemberExpression) && (e.NodeType == ExpressionType.MemberAccess))
                {
                    MemberExpression m = e as MemberExpression;
                    return new RaisingStudio.Data.Expressions.ColumnExpression[] { new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name) };
                }
                else if ((e is NewExpression) && (e.NodeType == ExpressionType.New))
                {
                    NewExpression n = e as NewExpression;
                    List<RaisingStudio.Data.Expressions.ColumnExpression> columnExpressionList = new List<RaisingStudio.Data.Expressions.ColumnExpression>();
                    foreach (var a in n.Arguments)
                    {
                        if ((a is MemberExpression) && (a.NodeType == ExpressionType.MemberAccess))
                        {
                            MemberExpression m = a as MemberExpression;
                            columnExpressionList.Add(new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name));
                        }
                    }
                    return columnExpressionList.ToArray();
                }
                else if((e is NewArrayExpression) && (e.NodeType == ExpressionType.NewArrayInit))
                {
                    NewArrayExpression n = e as NewArrayExpression;
                    List<RaisingStudio.Data.Expressions.ColumnExpression> columnExpressionList = new List<RaisingStudio.Data.Expressions.ColumnExpression>();
                    foreach (var a in n.Expressions)
                    {
                        if ((a is MemberExpression) && (a.NodeType == ExpressionType.MemberAccess))
                        {
                            MemberExpression m = a as MemberExpression;
                            columnExpressionList.Add(new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name));
                        }
                    }
                    return columnExpressionList.ToArray();
                }
            }
            return null;
        }

        public static RaisingStudio.Data.Expressions.ColumnExpression GetColumnExpression(Expression expression)
        {
            if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Quote))
            {
                expression = (expression as UnaryExpression).Operand;
            }
            if ((expression is LambdaExpression) && (expression.NodeType == ExpressionType.Lambda))
            {
                LambdaExpression l = expression as LambdaExpression;
                Expression e = l.Body;
                if (e.NodeType == ExpressionType.Convert)
                {
                    e = (e as UnaryExpression).Operand;
                }
                if ((e is MemberExpression) && (e.NodeType == ExpressionType.MemberAccess))
                {
                    MemberExpression m = e as MemberExpression;
                    return new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name);
                }
                else if ((e is NewExpression) && (e.NodeType == ExpressionType.New))
                {
                    NewExpression n = e as NewExpression;
                    foreach (var a in n.Arguments)
                    {
                        if ((a is MemberExpression) && (a.NodeType == ExpressionType.MemberAccess))
                        {
                            MemberExpression m = a as MemberExpression;
                            return new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name);
                        }
                    }
                }
            }
            return null;
        }


        public static string[] GetColumnNames(RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            if (columns != null)
            {
                string[] columnNames = new string[columns.Length];
                for (int i = 0; i < columns.Length; i++)
                {
                    columnNames[i] = (string)columns[i];
                }
                return columnNames;
            }
            return null;
        }
        #endregion

        private RaisingStudio.Data.Common.Builders.CommonCommandBuilder commandBuilder;
        public RaisingStudio.Data.Common.Builders.CommonCommandBuilder CommandBuilder
        {
            get { return commandBuilder; }
            set { commandBuilder = value; }
        }


        #region Constructor
        public ExpressionCommandBuilder(System.Linq.Expressions.Expression expression) : base(expression)
        {            
        }

        public ExpressionCommandBuilder(RaisingStudio.Data.Common.Builders.CommonCommandBuilder commonCommandBuilder, System.Linq.Expressions.Expression expression) : base(expression)
        {
            this.commandBuilder = commonCommandBuilder;
        }
        #endregion


        public CommandSegment GetCommand()
        {
            if (this.expression != null)
            {
                if (this.commandSegment == null)
                {
                    Build();
                }
                return this.commandSegment;
            }            
            else
            {
                return null;
            }
        }

        public CommandSegment GetCommand(out RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            columns = null;   
            if (this.expression != null)
            {
                if (this.commandSegment == null)
                {
                    Build();
                }
                columns = this.columnExpressions.ToArray();
                return this.commandSegment;
            }
            else
            {
                return null;
            }
        }


        public CommonCommand GetCommand(out CommandSegmentType segmentType)
        {
            CommandSegment commandSegment = this.GetCommand();
            segmentType = commandSegment.SegmentType;
            return commandSegment.CommonCommand;
        }

        public CommonCommand GetCommand(out RaisingStudio.Data.Expressions.ColumnExpression[] columns, out CommandSegmentType segmentType)
        {
            CommandSegment commandSegment = this.GetCommand(out columns);
            segmentType = commandSegment.SegmentType;
            return commandSegment.CommonCommand;
        }

        public CommonCommand GetCommand(out string[] columnNames)
        {
            CommandSegmentType segmentType;
            return this.GetCommand(out columnNames, out segmentType);
        }

        public CommonCommand GetCommand(out string[] columnNames, out CommandSegmentType segmentType)
        {
            RaisingStudio.Data.Expressions.ColumnExpression[] columns;
            CommonCommand command = this.GetCommand(out columns, out segmentType);
            columnNames = GetColumnNames(columns);
            return command;
        }


        private CommandSegment commandSegment;
        private Stack<CommandSegment> segmentStack = new Stack<CommandSegment>();
        private System.Data.Common.DataTableMapping dataTableMapping;

        public override void Build()
        {
            this.segmentStack.Clear();
            this.sortingExpressions.Clear();
            this.columnExpressions.Clear();
            this.parameterIndex = 0;

            if ((expression is MethodCallExpression) && ((expression as MethodCallExpression).Method.Name == "Count") && ((expression as MethodCallExpression).Arguments[0] is ConstantExpression))
            {
                CommonCommand command = this.commandBuilder.GetDataCountCommand();
                this.commandSegment = new CommandSegment(command, CommandSegmentType.Select);
            }
            else
            {
                ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator();
                Expression evaluatedExpression = expressionEvaluator.Evaluate(expression);

                this.Visit(evaluatedExpression);

                CommandSegmentType finalSegmentType = CommandSegmentType.None;
                CommonCommand command = null;
                CommandSegment commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                CommonCommand condition = null;
                if (commandSegment != null)
                {
                    if (commandSegment.SegmentType == CommandSegmentType.Select)
                    {
                        command = commandSegment.CommonCommand;
                        finalSegmentType |= CommandSegmentType.Select;

                        commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                        condition = GenerateConditionCommand(commandSegment);
                    }
                    else if ((commandSegment.SegmentType == CommandSegmentType.Binary)||(commandSegment.SegmentType == CommandSegmentType.Condition))
                    {
                        condition = GenerateConditionCommand(commandSegment);
                    }
                }
                #region condition
                if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                {
                    if (command != null)
                    {
                        command = commandBuilder.AppendConditionCommand(command, condition);
                    }
                    else
                    {
                        command = condition;
                    }
                    finalSegmentType |= CommandSegmentType.Condition;
                }
                #endregion
                #region sorting
                CommonCommand sorting = GenerateSortingCommand();
                if ((sorting != null) && (!string.IsNullOrEmpty(sorting.CommandText)))
                {
                    if (command != null)
                    {
                        command = this.commandBuilder.AppendConditionCommand(command, sorting);
                    }
                    else
                    {
                        command = sorting;
                    }
                    finalSegmentType |= CommandSegmentType.Sorting;
                }
                #endregion
                this.commandSegment = new CommandSegment(command, finalSegmentType);
            }
        }

        private CommonCommand GenerateConditionCommand(CommandSegment commandSegment)
        {
            CommonCommand condition = null;
            if (commandSegment != null)
            {
                if (commandSegment.SegmentType == CommandSegmentType.Condition)
                {
                    condition = commandSegment.CommonCommand;
                }
                else if (commandSegment.SegmentType == CommandSegmentType.Binary)
                {
                    string conditionCommandText = string.Format("WHERE {0}", commandSegment.CommonCommand.CommandText);
                    condition = new CommonCommand(conditionCommandText);
                    foreach (CommonParameter parameter in commandSegment.CommonCommand.Parameters)
                    {
                        condition.Parameters.Add(parameter);
                    }
                }
            }
            return condition;
        }

        private CommonCommand GenerateSortingCommand()
        {
            CommonCommand sortingCommand = new CommonCommand();
            string commandText = string.Empty;
            StringBuilder sortingExpressionStringBuilder = new StringBuilder();
            if (this.sortingExpressions.Count > 0)
            {
                sortingExpressionStringBuilder.Append("ORDER BY ");

                bool sortingExpressionStringSplitted = false;
                foreach (RaisingStudio.Data.Expressions.SortingExpression sortingExpression in this.sortingExpressions)
                {
                    string sortingExpressionString = RaisingStudio.Data.Common.Builders.Expressions.ConditionExpressionCommandBuilder.GetSortingExpressionString(sortingExpression);
                    if (sortingExpressionStringSplitted)
                    {
                        sortingExpressionStringBuilder.Append(", ");
                    }
                    sortingExpressionStringBuilder.Append(sortingExpressionString);
                    sortingExpressionStringSplitted = true;
                }

                if (!string.IsNullOrEmpty(commandText))
                {
                    commandText = string.Format("{0} {1}", commandText, sortingExpressionStringBuilder);
                }
                else
                {
                    commandText = sortingExpressionStringBuilder.ToString();
                }
            }
            sortingCommand.CommandText = commandText;
            return sortingCommand;
        }


        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b != null)
            {
                string operratorString;
                switch (b.NodeType)
                {
                    case ExpressionType.Equal:
                        {
                            operratorString = "=";
                            break;
                        }
                    case ExpressionType.NotEqual:
                        {
                            operratorString = "<>";
                            break;
                        }
                    case ExpressionType.GreaterThan:
                        {
                            operratorString = ">";
                            break;
                        }
                    case ExpressionType.GreaterThanOrEqual:
                        {
                            operratorString = ">=";
                            break;
                        }
                    case ExpressionType.LessThan:
                        {
                            operratorString = "<";
                            break;
                        }
                    case ExpressionType.LessThanOrEqual:
                        {
                            operratorString = "<=";
                            break;
                        }

                    case ExpressionType.AndAlso:
                        {
                            operratorString = "AND";
                            break;
                        }
                    case ExpressionType.OrElse:
                        {
                            operratorString = "OR";
                            break;
                        }

                    case ExpressionType.Add:
                        {
                            operratorString = "+";
                            break;
                        }
                    case ExpressionType.Subtract:
                        {
                            operratorString = "-";
                            break;
                        }
                    case ExpressionType.Multiply:
                        {
                            operratorString = "*";
                            break;
                        }
                    case ExpressionType.Divide:
                        {
                            operratorString = "/";
                            break;
                        }
                    case ExpressionType.Modulo:
                        {
                            operratorString = "%";
                            break;
                        }

                    default:
                        {
                            throw new NotSupportedException(b.NodeType + " is not supported.");
                        }
                }

                this.Visit(b.Left);
                this.Visit(b.Right);

                CommonCommand right = this.segmentStack.Pop().CommonCommand;
                CommonCommand left = this.segmentStack.Pop().CommonCommand;

                string commandText = String.Format("({0} {1} {2})", left, operratorString, right);
                CommonCommand command = new CommonCommand(commandText);
                foreach (var parameter in left.Parameters)
                {
                    command.Parameters.Add(parameter);
                }
                foreach (var parameter in right.Parameters)
                {
                    command.Parameters.Add(parameter);
                }

                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Binary });
            }
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            if (c != null)
            {
                string parameterName = string.Format(PARAMETERNAMEFORMAT, this.parameterIndex++);
                CommonCommand command = new CommonCommand(parameterName);
                command.Parameters.Add(parameterName, c.Value);
                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Parameter });
            }
            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m != null)
            {
                PropertyInfo propertyInfo = m.Member as PropertyInfo;
                if (propertyInfo != null)
                {
                    this.segmentStack.Push(new CommandSegment { CommonCommand = new CommonCommand(string.Format("[{0}]", propertyInfo.Name)), SegmentType = CommandSegmentType.Column });
                    this.columnStack.Push(new RaisingStudio.Data.Expressions.ColumnExpression(propertyInfo.Name));
                }
            }
            return m;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m != null)
            {
                MethodInfo methoInfo = m.Method;
                if (methoInfo != null)
                {
                    switch (methoInfo.Name)
                    {
                        #region order
                        case "OrderBy":
                        case "ThenBy":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    this.Visit(m.Arguments[1]);
                                    {
                                        if (this.columnStack.Count > 0)
                                        {
                                            RaisingStudio.Data.Expressions.ColumnExpression columnExpression = this.columnStack.Pop();
                                            this.sortingExpressions.Add(new RaisingStudio.Data.Expressions.SortingExpression(columnExpression));
                                            this.segmentStack.Pop();
                                        }
                                    }
                                }
                                return m;
                            }
                        case "OrderByDescending":
                        case "ThenByDescending":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    this.Visit(m.Arguments[1]);
                                    {
                                        if (this.columnStack.Count > 0)
                                        {
                                            RaisingStudio.Data.Expressions.ColumnExpression columnExpression = this.columnStack.Pop();
                                            this.sortingExpressions.Add(new RaisingStudio.Data.Expressions.SortingExpression(columnExpression, RaisingStudio.Data.Expressions.SortingDirection.Descending));
                                            this.segmentStack.Pop();
                                        }
                                    }
                                }
                                return m;
                            }
                        #endregion
                        #region select
                        case "Select":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    RaisingStudio.Data.Expressions.ColumnExpression[] columns = GetColumnExpressions(m.Arguments[1]);
                                    if (columns != null)
                                    {
                                        this.columnExpressions.AddRange(columns);
                                    }
                                }
                                return m;
                            }
                        #endregion
                        #region function
                        case "Count":
                            {
                                this.Visit(m.Arguments[0]);
                                CommonCommand command = this.commandBuilder.GetDataCountCommand();
                                commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                CommonCommand condition = GenerateConditionCommand(commandSegment);
                                if((condition != null)&&(!string.IsNullOrEmpty(condition.CommandText)))
                                {
                                    command = this.commandBuilder.AppendConditionCommand(command, condition);
                                }
                                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                return m;
                            }
                        case "Average":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetAvgDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        case "Max":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetMaxDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        case "Min":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetMinDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        case "Sum":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetSumDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        #endregion
                        #region string funciton
                        case "StartsWith":
                            {
                                // TODO:
                                return m;
                            }
                        case "Contains":
                            {
                                // TODO:
                                return m;
                            }
                        case "EndsWith":
                            {
                                // TODO:
                                return m;
                            }
                        #endregion
                    }
                }
            }
            // TODO: call method.
            return base.VisitMethodCall(m);
        }
    }
}
